/* 

==========================================================

DX490a - Summer 2010

Instructor: Stelios Manousakis

==========================================================

Class 13.2:

SuperCollider as a visual programming environment

Contents:

• Drawing and animation

- Pen

- SCDraw / SCDrawServer

• Working with Images

- SCImage

- SCImageFilter

• Quartz Composer in SC

- SCQuartzComposerView

==========================================================

*/



// ================= SUPERCOLLIDER AS A VISUAL PROGRAMMING ENVIRONMENT =================


// Probably the last thing that comes to mind when thinking about SuperCollider are its graphics capabilities, but the truth is that it can be quite powerful in this aspect. SC draws in a similar manner as Processing does, but has a far superior algorithmic backbone. Of course, it is not optimized for graphics, so it can be slow, but one can integrate Quartz Composer  (in Mac OS X), opening .qtz patches within SC and without having to bother with OSC communication. 




// ====== DRAWING AND ANIMATION ======


// ------ Pen --

// Pen is a class that allows you to algorithmically draw inside an Window (SCPen and SCWindow for CocoaGUI). 

// To draw, you need to call the drawHook function or the drawFunc function (when drawing inside an UserView/SCUserView), and then refresh the window to view the results of your actions.


// Here is an example by James McCartney:

(

// By James McCartney

var w, h = 700, v = 700, seed, run = true, phase = 0;

w = Window("wedge", Rect(40, 40, h, v), false);

w.view.background = Color.rand(0,0.3);

w.onClose = { run = false }; // stop the thread on close

w.front;

// store an initial seed value for the random generator

seed = Date.seed;

w.drawHook = {

Pen.width = 2;

Pen.use {

// reset this thread's seed for a moment

thisThread.randSeed = Date.seed;

// now a slight chance of a new seed or background color

if (0.006.coin) { seed = Date.seed; };

if (0.02.coin) { w.view.background = Color.rand(0,0.3); };

// either revert to the stored seed or set the new one

thisThread.randSeed = seed;

// the random values below will be the same each time if the seed has not changed

// only the phase value has advanced

Pen.translate(h/2, v/2);

// rotate the whole image

// negative random values rotate one direction, positive the other

Pen.rotate(phase * 1.0.rand2);

// scale the rotated y axis in a sine pattern

Pen.scale(1, 0.3 * sin(phase * 1.0.rand2 + 2pi.rand) + 0.5 );

// create a random number of annular wedges

rrand(6,24).do {

Pen.color = Color.rand(0.0,1.0).alpha_(rrand(0.1,0.7));

Pen.beginPath;

Pen.addAnnularWedge(Point(0,0), a = rrand(60,300), a + 50.rand2, 2pi.rand 

+ (phase * 2.0.rand2), 2pi.rand);

if (0.5.coin) {Pen.stroke}{Pen.fill};

};

};

};


// fork a thread to update 20 times a second, and advance the phase each time

{ while { run } { w.refresh; 0.05.wait; phase = phase + 0.01pi;} }.fork(AppClock)


)



// For a couple more demos look at these files:

Document.open("examples/GUI examples/rotary hommage duchamp.scd")

Document.open("examples/GUI examples/Nick's LetterGimmick.scd")



// You should also check out Frederik Olofsson's blog, where he has a tutorial and several very nice examples of AudioVisuals with SC:

"open http://www.fredrikolofsson.com/f0blog/?q=node/316".unixCmd

and:

"open http://www.fredrikolofsson.com/f0blog/files/audiovisuals_with_sc-examples.zip".unixCmd


// You may also want to get these extensions to the Pen class:

"open http://swiki.hfbk-hamburg.de:8888/MusicTechnology/743".unixCmd



// ------ SCDraw / SCDrawServer --

// These two classes are in the SCAnimation quark by Don Craig, and are intended for realtime (SCDrawServer) and non-realtime (SCDraw) procedural animation. These classes use Pen, so they can do everything Pen can, plus some extra things. Have a look at the help-files for more information and some examples.

Quarks.install( "SCAnimation", checkoutIfNeeded: false)




// ====== WORKING WITH IMAGES ======


// ------ SCImage --

// You can load, create and save images within SC using the SCImage class.


// Load an image from your computer

i = SCImage.new("/Library/Desktop Pictures/Ripples Blue.jpg");

[i.width, i.height].postln;

i.plot;

i.free;

// Or, load an image from the web: URL string - http:// or ftp:// - blocks until image is downloaded

i = SCImage.new("http://www.washington.edu/dxarts/images/logo.gif");

i.plot;

i.url;

i.free;


// Let's do some simple operations to an image:

// add an image to a window

w = SCWindow.new.front;

w.view.background = Color(0.6,0.2,0.2);

i = SCImage.new("http://www.washington.edu/dxarts/images/logo.gif");

w.view.backgroundImage_(i, 1, 0.6); // (image, tileMode, alpha);


// grab the image:

g = SCImage.fromWindow(w, Rect(0, 0, 85, 95));

g.plot(freeOnClose:true);

w.close;


// create another window;

w = SCWindow.new.front;

w.view.background = Color(0.2,0.6,0.6);

w.view.backgroundImage_(g, 4, 0.6); // (image, tileMode, alpha)

g.free;


// grab the new image, and create another window

j = SCImage.fromWindow(w, Rect(0, 0, 190, 190));

j.plot(freeOnClose:true);

j.write("~/Desktop/my_drawing.png"); // write the image

w.close;

j.free


// Now, create a 2D slider with that image as a background

(

a = SCImage.new("~/Desktop/my_drawing.png");

w = SCWindow.new("SCImage background");

l = SC2DSlider.new(w, Rect(10,10,190,190)).backgroundImage_(a, 4, 1);

w.front;

a.free; // safe

)


l.knobColor = Color(1, 0, 0, 0.4)



// For more information, see the SCImage helpfile






// ------ SCImageFilter --

// You can apply CoreImage Filters to an SCImage, using the SCImageFilter - and there are dozens of filters available!!!!

// You can find a list and more info about these filters here:

// http://developer.apple.com/documentation/GraphicsImaging/Reference/CoreImageFilterReference/Reference/reference.html




(

// the image to use; scaled-down for faster computation

a = SCImage.new("/Library/Desktop Pictures/Plants/Peony.jpg").scalesWhenResized_(true).setSize(500, 400);

a.plot; 

)


(// posterize 

h = SCImageFilter.new(\CIColorPosterize);

a.addFilter(h); // first call needed

a.plot; // there should have it

)


(// Roy Lichtenstein

h = SCImageFilter.new(\CIComicEffect);

a.addFilter(h); // first call needed

a.plot; // there should have it

)


(// a glass blur

h = SCImageFilter.new(\CIGlassLozenge);

a.addFilter(h); // first call needed

a.plot; // there should have it

)



(// Kaleidoscope

h = SCImageFilter.new(\CIKaleidoscope);

h.count_(6); // h

a.addFilter(h); // first call needed

a.plot; // there should have it

)



(// image adjustments

h = SCImageFilter.new(\CIColorControls);

h.saturation_(0.5).brightness_(0.2).contrast_(0.7);

a.addFilter(h); // first call needed

a.plot; // there should have it

)


(// Add glow

h = SCImageFilter.new(\CIBloom);

a.addFilter(h); // first call needed

a.plot; // there should have it

)



(// Invert colors

h = SCImageFilter.new(\CIColorInvert);

a.addFilter(h); // first call needed

a.plot; // there should have it

)


// Image masking and sequences of filters:

(

f = SCImageFilter.new(\CIColorMonochrome); // create a GrayScale image

g = SCImageFilter.new(\CISourceInCompositing); // compositing we will use

f.color_(Color.black);

f.intensity_(1.0);

b = SCImage.new("~/Desktop/my_drawing.png"); // use the first image we created in this file, and saved onto the destop

// apply a sequence of filters:

a.applyFilters(

[f, SCImageFilter(\CIColorInvert), 

SCImageFilter(\CIMaskToAlpha)]

); // grayscale + invert + maskToAlpha = create a mask

g.backgroundImage_(a); // set up background image

b.applyFilters(g); // create masked image

a.free;

w = b.plot(freeOnClose:true, background:Color.clear); // set to clear color to see plainly the image

)


// You can also load Non-built In Plugins (add them in /Library/Graphics/Image Units/).

// Get some free plug-ins here:

"open http://www.noiseindustries.com/products".unixCmd



// For more information, see the SCImageFilter helpfile





// ====== QUARTZ COMPOSER IN SC ======


// ------ SCQuartzComposerView --

// Quartz Composer is a Apple's visual programming environment using objects and patchcords (similar idea as with Max/MSP, PureData and many other environments) for processing and rendering graphics in Mac OS X. It comes free with the Apple Developer Tools and is extremely efficient, as it is highly optimized for OS X and Apple comptuer hardware.


// SCQuartzComposerView is a class that let's you integrate QC compositions within SuperCollider, allowing you to merge the best of the two worlds: QC optimized graphics and SC's algorithmic capabilities (QC really lacks on that aspect).

// When making a composition/patch in QC, you can choose to publich inputs and outputs; the names that you give these inputs and outputs are kept in a dictionary, and can be accessed from within SC using the methods setInputValue, getInputValue and getOutputValue



// • Here is a simple example, only loading a QC composition, playing and stopping it:


(

// make a window for the viewer

w = SCWindow("Simple QC Test").front;

// create a button to load patches

b = SCButton(w, Rect(0, 0, 150, 20))

.states_([["pick another QC file"]])

.action_({ File.openDialog("", { |path| m.path_(path) }) });

// create the QC viewer

m = SCQuartzComposerView(w, Rect(0,20,400, 260));

// add the path of the patch to use

m.path = "/Applications/SuperCollider/Help/GUI/Cocoa-GUI/SCQuartzComposerView" ++ "/Cells.qtz";

)

m.start; // start it

m.stop; // stop it



 

// • Now, a more involved example, using QC's inputs for controlling a composition from SC.

(

~qcWin = SCWindow("Kandinsky", Rect(0,0,720,576), border: false).front;

b = SCButton(~qcWin, Rect(0, 0, 150, 20))

.states_([["Close Me"]])

.action_({~qcWin.close});

q = SCQuartzComposerView(~qcWin, Rect(0,20,720,576));

// put the "QuartzComposer_patch_support" folder and all its contents together with this file for it to work

q.path = Document.current.path.dirname ++ "/QuartzComposer_patch_support/Simple-QC_Kandinsky.qtz";

q.resize = 5;

q.start;

)


// get the names of input and output keys

q.inputKeys;

q.outputKeys;

// you can see the published inputs and outputs in the composition inside Quartz Composer if you want

q.openInQC;



// Now, to control this composition directly from within SC.

// mapping envelopes to use in the function, one env for each input

~xEnv = Env([0, 1, 0.2, 0.4, 0.8].scramble, [1, 1.5, 3, 0.5].scramble.normalizeSum, [-2, 5, 4, 8].scramble);

~yEnv = Env([0, 1, 0.2, 0.4, 0.8].scramble, [1, 1.5, 3, 0.5].scramble.normalizeSum, [-2, 5, 4, 8].scramble);


(

// a function that includes a task to control each input in real time, and a task per parameter for recreating the envelopes in real-time

~imgMove = {arg xmul, ymul, speed, jit;

var task, envSpeed;

envSpeed = speed * 1500;

envSpeed.postln;

t = Task{

inf.do({|i|

q.x_pos_((jit[0] * -1).rrand(jit[0]) + xmul * ~xEnv[((i % 1000) * 0.001)]);

speed[0].wait(speed[1]);

})

};

p = Task{

inf.do({|i|

q.y_pos_((jit[1] * -1).rrand(jit[1]) + ymul * ~yEnv[((i % 1000) * 0.001)]);

speed[0].wait(speed[1]);

})

};

e = Task{

inf.do({|i|

~xEnv = Env([0, 1, 0.2, 0.4, 0.8].scramble, [1, 1.5, 3, 0.5].scramble.normalizeSum, [-2, 5, 4, 8].scramble);

envSpeed[0].wait(envSpeed[1]);

})

};

f = Task{

inf.do({|i|

~yEnv = Env([0, 1, 0.2, 0.4, 0.8].scramble, [1, 1.5, 3, 0.5].scramble.normalizeSum, [-2, 5, 4, 8].scramble);

envSpeed[0].wait(envSpeed[1]);

})

};

t.play(AppClock);

p.play(AppClock);

e.play(AppClock);

f.play(AppClock);

};

)


// start the function

~imgMove.value(1, 1, [0.05, 0.25], [0.5, 0.005])

// fullscreen mode if you like

~qcWin.fullScreen

// individual control over each task

p.stop;

t.stop; 

e.stop;

f.stop;

p.start; 

t.start;

e.start;

f.start;




// • You can also use QC to create GUIs as controls for your synths:

// Here is an example from the SCQuartzComposerView help file (there are more there)


////////// Control some audio: Stupid Pan Example


(

w = SCWindow("Stupid Pan Example", Rect(0,20,600, 150)).front;

m = SCQuartzComposerView(w, Rect(0,20,600, 100));

m.path = Document.current.path.dirname ++ "/Stupid Pan.qtz";

m.resize = 5;

m.start;

)


s.boot;

// use mouse to set pan position

(

{

loop({

{ Pan2.ar(Saw.ar(mul: 0.1) * EnvGen.ar(Env.perc, timeScale: 4, doneAction: 2), m.x_pos) }.play;

1.wait;

});

}.fork(AppClock);

)




// Here is an online repository of QC patches:

"open http://kineme.net/".unixCmd